package kaist.sep502.tsp;

import java.util.ArrayList;
import java.util.List;

import kaist.sep502.tsp.algorithm.search.AStarSearch;
import kaist.sep502.tsp.algorithm.search.ApproximatedSolution;
import kaist.sep502.tsp.algorithm.search.IdsAndAStarSearch;
import kaist.sep502.tsp.algorithm.search.IterativeDeepeningSearch;
import kaist.sep502.tsp.algorithm.search.SearchAlgorithm;
import kaist.sep502.tsp.algorithm.search.UniformCostSearch;
import kaist.sep502.tsp.model.City;
import kaist.sep502.tsp.model.Problem;
import kaist.sep502.tsp.model.Solution;
import kaist.sep502.tsp.util.TspUtil;
import kaist.sep502.tsp.view.CityMapView;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.progress.UIJob;

public class NavigationView extends ViewPart {
	/** */
	private static final String UNIFORM_COST = "Uniform Cost";
	/** */
	private static final String APPROXIMATED_SOLUTION = "Approximated Solution";
	/** */
	private static final String ITERATIVE_DEEPENING_A_STAR = "Iterative Deepening A*";
	/** */
	private static final String A_STAR = "A*";
	/** */
	private static final String ITERATIVE_DEEPENING = "Iterative Deepening";
	public static final String ID = "kaist.sep502.tsp.navigationView";
	private FormToolkit toolkit;
	private ScrolledForm form;
	private Combo algorithmCombo;
	public List<City> loadedCityList;
	
	private Label algoLabel;
	private Label cityNoLabel;
	private Label timeLabel;
	private Label nodeLabel;
	private Text pathLabel;
	private Label costLabel;
//	private Label meanCostLabel;
//	private Label maxCostLabel;
//	private Label minCostLabel;
	
	private Problem problem;
	
	Text cityText;
    
    public void createPartControl(Composite parent) {
    	toolkit = new FormToolkit(parent.getDisplay());
    	form = toolkit.createScrolledForm(parent);
    	form.setText("Menu");
    	form.getBody().setLayout(new FormLayout());
    	
    	
    	// Simualtion
		Button simulationButton = toolkit.createButton(form.getBody(), "SIMULATION", SWT.PUSH);
		simulationButton.setToolTipText("Click this button to simualte selected tsp algorithm");
		FormData fd = new FormData();
		fd.top = new FormAttachment(0, 5);
		fd.left = new FormAttachment(0, 5);
		fd.right = new FormAttachment(100, -5);
    	simulationButton.setLayoutData(fd);
    	final FileDialog dialog = new FileDialog(parent.getShell(), SWT.OPEN);
    	simulationButton.addSelectionListener(new SelectionListener() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				if(loadedCityList == null || loadedCityList.size() == 0){
					String cityFilePath = dialog.open();
					if(cityFilePath != null){
						initProblem(cityFilePath);
					}
				}
				
				search();
			}

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}
		});
    	
    	// File choose Button
		Button fileButton = toolkit.createButton(form.getBody(), "Load City Information", SWT.PUSH);
		fd = new FormData();
		fd.top = new FormAttachment(simulationButton, 5);
		fd.left = new FormAttachment(0, 5);
		fd.right = new FormAttachment(100, -5);
		fileButton.setLayoutData(fd);
		fileButton.addSelectionListener(new SelectionListener() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				String cityFilePath = dialog.open();
				if(cityFilePath != null){
					initProblem(cityFilePath);
				}
			}
			
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});
		
		// Select Algorithm
		algorithmCombo = new Combo(form.getBody(), SWT.READ_ONLY);
		algorithmCombo.setItems(new String[]{UNIFORM_COST, ITERATIVE_DEEPENING, A_STAR, ITERATIVE_DEEPENING_A_STAR, APPROXIMATED_SOLUTION});
		algorithmCombo.select(1);
		fd = new FormData();
		fd.top = new FormAttachment(fileButton, 5);
		fd.left = new FormAttachment(0, 5);
		fd.right = new FormAttachment(100, -5);
		algorithmCombo.setLayoutData(fd);
		
		// City Count
		Label cityLabel =  toolkit.createLabel(form.getBody(), "Cities");
		cityText = toolkit.createText(form.getBody(), "13");
		cityText.addVerifyListener(new VerifyListener() {
			@Override
			public void verifyText(VerifyEvent e) {
				e.doit = e.text.length() == 0 || Character.isDigit(e.text.charAt(0));
			}
		});
		fd = new FormData();
		fd.top = new FormAttachment(algorithmCombo, 5);
		fd.left = new FormAttachment(0, 5);
		cityLabel.setLayoutData(fd);
		fd = new FormData();
		fd.top = new FormAttachment(algorithmCombo, 5);
		fd.left = new FormAttachment(cityLabel, 5);
		fd.right = new FormAttachment(100, -5);
		cityText.setLayoutData(fd);
		cityText.addKeyListener(new KeyListener() {
			@Override
			public void keyReleased(KeyEvent e) {
			}
			
			@Override
			public void keyPressed(KeyEvent e) {
				if(e.keyCode == SWT.CR){
					search();
				}
			}
		});
		
		// 결과 창
		Label rs001Label =  toolkit.createLabel(form.getBody(), "");
		Label rs002Label =  toolkit.createLabel(form.getBody(), "-- RESULT --");
		Label rs003Label =  toolkit.createLabel(form.getBody(), "Algorithm :");
		algoLabel =  toolkit.createLabel(form.getBody(), "");
		Label rs02Label =  toolkit.createLabel(form.getBody(), "cities :");
		cityNoLabel =  toolkit.createLabel(form.getBody(), "");
		Label rs1Label =  toolkit.createLabel(form.getBody(), "Elapsed Time :");
		timeLabel =  toolkit.createLabel(form.getBody(), "");
		Label rs2Label =  toolkit.createLabel(form.getBody(), "Expanded Nodes :");
		nodeLabel =  toolkit.createLabel(form.getBody(), "");
		Label rs4Label =  toolkit.createLabel(form.getBody(), "Path Cost :");
		costLabel =  toolkit.createLabel(form.getBody(), "");
//		Label rs5Label =  toolkit.createLabel(form.getBody(), "Mean Cost :");
//		meanCostLabel =  toolkit.createLabel(form.getBody(), "", SWT.RIGHT);
//		Label rs6Label =  toolkit.createLabel(form.getBody(), "Max Cost :");
//		maxCostLabel =  toolkit.createLabel(form.getBody(), "", SWT.RIGHT);
//		Label rs8Label =  toolkit.createLabel(form.getBody(), "Min Cost :");
//		minCostLabel =  toolkit.createLabel(form.getBody(), "", SWT.RIGHT);
		Label rs3Label =  toolkit.createLabel(form.getBody(), "Path :");
		pathLabel =  toolkit.createText(form.getBody(), "", SWT.WRAP | SWT.READ_ONLY | SWT.MULTI);
		
		
		fd = new FormData(); fd.top = new FormAttachment(cityText, 5); 	fd.left = new FormAttachment(0, 5);	fd.right = new FormAttachment(100, -5);
		rs001Label.setLayoutData(fd);
		
		fd = new FormData(); fd.top = new FormAttachment(rs001Label, 5); 	fd.left = new FormAttachment(0, 5);	fd.right = new FormAttachment(100, -5);
		rs002Label.setLayoutData(fd);
		
		fd = new FormData(); fd.top = new FormAttachment(rs002Label, 5); fd.left = new FormAttachment(0, 5);	//fd.right = new FormAttachment(100, -5);
		rs003Label.setLayoutData(fd);
		
		fd = new FormData(); fd.top = new FormAttachment(rs002Label, 5); fd.left = new FormAttachment(rs003Label, 5);	fd.right = new FormAttachment(100, -5);
		algoLabel.setLayoutData(fd); algoLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
		
		fd = new FormData(); fd.top = new FormAttachment(algoLabel, 5); fd.left = new FormAttachment(0, 5);	//fd.right = new FormAttachment(100, -5);
		rs02Label.setLayoutData(fd);
		
		fd = new FormData(); fd.top = new FormAttachment(algoLabel, 5); fd.left = new FormAttachment(rs02Label, 5);	fd.right = new FormAttachment(100, -5);
		cityNoLabel.setLayoutData(fd); cityNoLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
		
		
		fd = new FormData(); fd.top = new FormAttachment(cityNoLabel, 5); fd.left = new FormAttachment(0, 5);	//fd.right = new FormAttachment(100, -5);
		rs1Label.setLayoutData(fd);
		
		fd = new FormData(); fd.top = new FormAttachment(cityNoLabel, 5); fd.left = new FormAttachment(rs1Label, 5);	fd.right = new FormAttachment(100, -5);
		timeLabel.setLayoutData(fd); timeLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
		
		fd = new FormData(); fd.top = new FormAttachment(timeLabel, 5); fd.left = new FormAttachment(0, 5);	//fd.right = new FormAttachment(100, -5);
		rs2Label.setLayoutData(fd);
		
		fd = new FormData(); fd.top = new FormAttachment(timeLabel, 5); fd.left = new FormAttachment(rs2Label, 5);	fd.right = new FormAttachment(100, -5);
		nodeLabel.setLayoutData(fd); nodeLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
		
		fd = new FormData(); fd.top = new FormAttachment(nodeLabel, 5); fd.left = new FormAttachment(0, 5);	//fd.right = new FormAttachment(100, -5);
		rs4Label.setLayoutData(fd);
		
		fd = new FormData(); fd.top = new FormAttachment(nodeLabel, 5); fd.left = new FormAttachment(rs4Label, 5); fd.right = new FormAttachment(100, -5);
		costLabel.setLayoutData(fd); costLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
		
//		fd = new FormData(); fd.top = new FormAttachment(costLabel, 5); 	fd.left = new FormAttachment(0, 5); //fd.right = new FormAttachment(100, -5);
//		rs5Label.setLayoutData(fd);
//		
//		fd = new FormData(); fd.top = new FormAttachment(costLabel, 5); 	fd.left = new FormAttachment(rs5Label, 5); 		fd.right = new FormAttachment(100, -5);
//		meanCostLabel.setLayoutData(fd);  meanCostLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
//		
//		fd = new FormData(); fd.top = new FormAttachment(meanCostLabel, 5); fd.left = new FormAttachment(0, 5); 	//	fd.right = new FormAttachment(100, -5);
//		rs6Label.setLayoutData(fd);
//		
//		fd = new FormData(); fd.top = new FormAttachment(meanCostLabel, 5); 	fd.left = new FormAttachment(rs6Label, 5); 		fd.right = new FormAttachment(100, -5);
//		maxCostLabel.setLayoutData(fd); maxCostLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
//		
//		fd = new FormData(); fd.top = new FormAttachment(maxCostLabel, 5);fd.left = new FormAttachment(0, 5); 	//	fd.right = new FormAttachment(100, -5);
//		rs8Label.setLayoutData(fd);
//		
//		fd = new FormData(); fd.top = new FormAttachment(maxCostLabel, 5); 	fd.left = new FormAttachment(rs8Label, 5); 		fd.right = new FormAttachment(100, -5);
//		minCostLabel.setLayoutData(fd); minCostLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
		
		fd = new FormData(); fd.top = new FormAttachment(costLabel, 5);	fd.left = new FormAttachment(0, 5);	fd.right = new FormAttachment(100, -5);
		rs3Label.setLayoutData(fd);
		
		fd = new FormData(); fd.top = new FormAttachment(rs3Label, 5); fd.bottom = new FormAttachment(100, -5);	
		fd.left = new FormAttachment(0, 5);	fd.right = new FormAttachment(100, -5);
		pathLabel.setLayoutData(fd); pathLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
    }
    
    void initProblem(String filePath){
    	// TODO 테스트용 삭제할 
//    	filePath = "/Users/starsir/DEV/workspace/rcp64/kaist.sep502.tsp1/doc/cities250.txt";
    	try {
			loadedCityList = TspUtil.readCityFile(filePath);
		} catch (Exception e) {
			Activator.addConsoleMessage("ERROR: " + e.getMessage(), true);
			Activator.log(e);
		} 
    	
    	if(loadedCityList.size() == 0){
    		Activator.addConsoleMessage("You have to select correct file and format.", true);
    		Activator.addConsoleMessage("format :", true);
    		Activator.addConsoleMessage("x-codinate  y-codinate <enter> ", true);
    		Activator.addConsoleMessage("Sample :", true);
    		Activator.addConsoleMessage("9.2181297e-001  5.6920575e-001", true);
    	} else {
    		for(int i=0; i<loadedCityList.size(); i++){
    			City city = loadedCityList.get(i);
    			StringBuilder sb = new StringBuilder();
    			sb.append("City No.: ").append(city.getCityNo()).append("       X:Y = ").append(TspUtil.formatDouble(city.getX())).append(" : ").append(TspUtil.formatDouble(city.getX()));
    			Activator.addConsoleMessage(sb, false);
    		}
    		Activator.addConsoleMessage("City Information is loaded successfully (" + loadedCityList.size() + " cities)", false);
    	}
    }
    
	private void search() {
		switch (algorithmCombo.getSelectionIndex()){
		case 0:
			searchByUniformCost();
			break;
		case 1:
			searchByIterativeDeepening();
			break;
		case 2:
			searchByAStar();
			break;
		case 3:
			searchByIDandAStar();
			break;
		case 4:
			searchByApproximatedSolution();
			break;
		}
	}
    
	private void searchByUniformCost() {
		String cityCount = cityText.getText();
		if(cityCount != null || !"".equals(cityCount)){
			if(Integer.parseInt(cityCount) > 9){
				IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
				MessageDialog.openError(window.getShell(), "Error", "IDS is too slow, so that you can use less than 9 cities");
				cityText.setText("8");
				return;
			}
		}
		
    	final CityMapView cityMapView = initSearch();
    	if(cityMapView == null)  return;
    	
    	
    	new UIJob("Search by using Uniform Cost"){

			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {
				monitor.beginTask("search...", 0);

				SearchAlgorithm searchAlgorithm = new UniformCostSearch(problem);
				Solution solution = searchAlgorithm.search();
				
				afterSearch(cityMapView, solution);
				monitor.done();
				return Status.OK_STATUS;
			}
    		
    	}.schedule();
	}
	
    private void searchByApproximatedSolution() {
    	final CityMapView cityMapView = initSearch();
    	if(cityMapView == null)  return;
    	
    	
    	new UIJob("Search by using Interative Deeppening"){

			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {
				monitor.beginTask("search...", 0);

				SearchAlgorithm searchAlgorithm = new ApproximatedSolution(problem);
				Solution solution = searchAlgorithm.search();
				
				afterSearch(cityMapView, solution);
				monitor.done();
				return Status.OK_STATUS;
			}
    		
    	}.schedule();
	}

	private void searchByIDandAStar() {
		final CityMapView cityMapView = initSearch();
    	if(cityMapView == null)  return;
    	
    	
    	new UIJob("Search by using Interative Deeppening"){

			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {
				monitor.beginTask("search...", 0);
				monitor.subTask("search");
				SearchAlgorithm searchAlgorithm = new IdsAndAStarSearch(problem);
				Solution solution = searchAlgorithm.search();
				
				afterSearch(cityMapView, solution);
				monitor.done();
				return Status.OK_STATUS;
			}
    		
    	}.schedule();
		
		
	}

	private void searchByIterativeDeepening() {
		final CityMapView cityMapView = initSearch();
    	if(cityMapView == null)  return;
    	
    	new UIJob("Search by using Interative Deeppening"){

			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {
				monitor.beginTask("search...", 0);
				monitor.subTask("search");
				final SearchAlgorithm searchAlgorithm = new IterativeDeepeningSearch(problem);
				Solution solution = searchAlgorithm.search();
				afterSearch(cityMapView, solution);
				monitor.done();
				return Status.OK_STATUS;
			}
    		
    	}.schedule();
	}
    
    public void searchByAStar(){
    	final CityMapView cityMapView = initSearch();
    	if(cityMapView == null)  return;
    	
    	new UIJob("Search by using Interative Deeppening"){

			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {
				monitor.beginTask("search...", 0);
				monitor.subTask("search");
				SearchAlgorithm searchAlgorithm = new AStarSearch(problem);
				Solution solution = searchAlgorithm.search();
				
				afterSearch(cityMapView, solution);
				monitor.done();
				return Status.OK_STATUS;
			}
    		
    	}.schedule();
		
		
	}

	/**
	 * @param cityMapView
	 * @param solution
	 */
	public void afterSearch(CityMapView cityMapView, Solution solution) {
		if(solution.isSuccess()){
			cityMapView.drawConnection(solution);
			Activator.addConsoleMessage("-------------------------------------------", false);
			Activator.addConsoleMessage(solution.successMessage().toString(), false);
			addResultMessage(solution);
			cityMapView.setStatusLine("it's done  :)");
		} else {
			Activator.addConsoleMessage("-------------------------------------------", false);
			Activator.addConsoleMessage("There is no expected result", true);
			cityMapView.setStatusLine("it's done, but failed to find solution  -.-;;");
		}
	}

	/**
	 * @return
	 */
	private CityMapView initSearch() {
		if(loadedCityList == null || loadedCityList.size() == 0){
    		Activator.addConsoleMessage("first, you have to load a file that has a city information(cities250.txt)", true);
			return null;
    	} 
    	
		List<City> myCityList = new ArrayList<City>(60);
		String cityCount = cityText.getText();
		if(cityCount == null || "".equals(cityCount)){
			Activator.addConsoleMessage("city size should be entered", true);
			return null;
		} else if(Integer.parseInt(cityCount) > loadedCityList.size()){
			int maxCitySize = loadedCityList.size();
			StringBuilder sb = new StringBuilder();
			sb.append("you have only ").append(maxCitySize).append(" cities. input city size should be less than ").append(maxCitySize);
    		Activator.addConsoleMessage(sb.toString(), true);
    		setStatusLine(sb.toString());
    		cityText.setText("" + maxCitySize);
			return null;
    	} else if(Integer.parseInt(cityCount) < 4){
			int maxCitySize = loadedCityList.size();
			StringBuilder sb = new StringBuilder();
			sb.append("you have to input city size more than 3").append(maxCitySize);
    		Activator.addConsoleMessage(sb.toString(), true);
    		setStatusLine(sb.toString());
    		cityText.setText("" + 4);
			return null;
    	}
		
		for(int i=0; i<Integer.parseInt(cityCount); i++){
			myCityList.add(loadedCityList.get(i));
		}
		
		problem = new Problem();
		try {
			problem.init(myCityList, 0, 0);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		
		// show cities in a map
		CityMapView cityMapView = null;
		
		IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); 
		if(window != null) {	
			try {
				cityMapView = (CityMapView) window.getActivePage().showView(CityMapView.ID, Integer.toString(CityMapView.instanceNum++), IWorkbenchPage.VIEW_ACTIVATE);
				if(cityMapView != null){
					cityMapView.setStatusLine("Please wait...");
					cityMapView.initCityMap(problem);
				}
			} catch (PartInitException e) {
				Activator.addConsoleMessage("ERROR: " + e.getMessage(), true);
				MessageDialog.openError(window.getShell(), "Error", "Error opening view:" + e.getMessage());
				Activator.log(e);
				return null;
			}
		}
		
		Activator.addConsoleMessage("Start to search...", true);
		addStartMessage();
		return cityMapView;
	}
    
    public void addResultMessage(Solution solution){
    	algoLabel.setText(solution.algorithmName);
		cityNoLabel.setText((solution.goalCityPath.tourHistory.size() -1) + "");
		timeLabel.setText(solution.getElanpsedTime());
		nodeLabel.setText("" + solution.expandedNodeCount);
		pathLabel.setText(solution.goalCityPath.getPathListString().toString());
		costLabel.setText("" + TspUtil.formatDouble(solution.goalCityPath.getPathCost()));
		
		/*
		meanCostLabel.setText("" + TspUtil.formatDouble(problem.meanPathCost));
		maxCostLabel.setText("" + TspUtil.formatDouble(problem.maxPathCost));
		minCostLabel.setText("" + TspUtil.formatDouble(problem.minPathCost));
		*/
    }
    
    public void addStartMessage(){
    	algoLabel.setText(algorithmCombo.getItem(algorithmCombo.getSelectionIndex()));
		cityNoLabel.setText("");
		timeLabel.setText("Please, wait...");
		nodeLabel.setText("...");
		pathLabel.setText("...");
		costLabel.setText("...");
    }


	/* (non-Javadoc)
	 * @see org.eclipse.ui.part.WorkbenchPart#dispose()
	 */
	@Override
	public void dispose() {
		toolkit.dispose();
		super.dispose();
	}
	
	public void setStatusLine(String message){
		IActionBars bars = getViewSite().getActionBars();
		bars.getStatusLineManager().setMessage(message);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
	 */
	@Override
	public void setFocus() {
		form.setFocus();
	}
}